package hdgeom.topomesh;

import hdgeom.primitives.AbstractFace;
import hdgeom.primitives.Box;
import hdgeom.primitives.Polygon2D;
import hdgeom.primitives.Vec3D;

import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.List;

import processing.core.PGraphics;
import processing.core.PMatrix3D;
import processing.core.PVector;

public class TopoMeshFace extends AbstractFace {
	public TopoMeshNode[] nodes;
	Object data;
	int comment;
	int id;
	final static float SMALL_NUM = 0.0000001f;
	public boolean fix = false;

	public TopoMeshFace() {
		nodes = new TopoMeshNode[0];
	}

	public TopoMeshFace(TopoMeshNode[] nodes) {
		this.nodes = nodes;
		// TODO Auto-generated constructor stub
	}

	public TopoMeshFace(TopoMeshNode n1, TopoMeshNode n2, TopoMeshNode n3) {
		this.nodes = new TopoMeshNode[] { n1, n2, n3 };
	}

	public TopoMeshFace(TopoMeshNode n1, TopoMeshNode n2, TopoMeshNode n3,
			TopoMeshNode n4) {
		this.nodes = new TopoMeshNode[] { n1, n2, n3, n4 };
	}

	public void inverse() {
		for (int i = 0; i < nodes.length / 2; i++) {
			TopoMeshNode temp = nodes[i];
			nodes[i] = nodes[nodes.length - i - 1];
			nodes[nodes.length - i - 1] = temp;
		}
	}

	public void turn() {
		TopoMeshNode[] newNodes = new TopoMeshNode[nodes.length];
		int j = 0;
		for (int i = nodes.length - 1; i >= 0; i--) {
			newNodes[j] = nodes[i];
			j++;
		}
		nodes = newNodes;
	}

	public ArrayList<TopoMeshEdge> getEdges() {
		ArrayList<TopoMeshEdge> edges = new ArrayList<TopoMeshEdge>();
		for (int i = 0; i < this.getNNodes(); i++) {
			TopoMeshNode node1 = (TopoMeshNode) getNode(i);
			TopoMeshNode node2 = (TopoMeshNode) getNode(i + 1);
			TopoMeshEdge edge = node1.getEdge(node2);
			if (edge != null)
				edges.add(edge);
		}
		return edges;
	}

	public ArrayList<TopoMeshFace> getNbFaces() {
		ArrayList<TopoMeshEdge> edges = this.getEdges();
		ArrayList<TopoMeshFace> faces = new ArrayList<TopoMeshFace>();
		for (int i = 0; i < edges.size(); i++) {
			TopoMeshEdge edge = edges.get(i);
			TopoMeshFace nbFace = edge.getOtherFace(this);
			if (nbFace != null) {
				faces.add(nbFace);
			}
		}
		// TODO Auto-generated method stub
		return faces;
	}

	public boolean contains(Vec3D pos) {
		for (int i = 0; i < getNNodes(); i++) {
			Vec3D n = getNode(i);
			if (n == pos)
				return true;
		}
		return false;
	}

	public TopoMeshFace getCopy() {
		TopoMeshFace face = new TopoMeshFace(nodes);
		face.setData(this.getData());
		face.setComment(this.getComment());
		return face;
	}

	public void addNode(TopoMeshNode node) {
		// TODO Auto-generated method stub
		TopoMeshNode[] newNodes = new TopoMeshNode[this.getNNodes() + 1];
		// Arrays.copyOf(nodes, nodes.length+1, Vec3D[].class);
		System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
		newNodes[newNodes.length - 1] = node;
		this.nodes = newNodes;
	}

	public TopoMeshNode getNode(int i) {
		i = i % getNNodes();
		return nodes[i];
	}

	public int getNNodes() {
		return nodes.length;
	}

	public Vec3D getNormal() {
		Vec3D n1 = Vec3D.getNormal(getNode(0), getNode(1), getNode(2));
		if (Float.isNaN(n1.z)) {
			n1 = new Vec3D(0, 0, 1);
		}
		if (getNNodes() == 3) {
			return n1;
		} else {
			Vec3D n2 = Vec3D.getNormal(getNode(2), getNode(3), getNode(0));
			if (Vec3D.angleBetween(n1, n2) > Math.PI - 0.01f) {
				n2.invert();
			}

			Vec3D sum = Vec3D.add(n1, n2);
			sum.mult(0.5f);
			sum.normalize();
			if (Float.isNaN(sum.x)) {

				System.out.println("same angle");
				System.out.println("n1" + n1);
				System.out.println("n2" + n2);
				return new Vec3D(0, 0, 1);
			}
			return sum;
		}
	}

	public Vec3D getNormalUnscaled() {
		Vec3D n1 = Vec3D.getNormalUnscaled(getNode(0), getNode(1), getNode(2));

		if (getNNodes() == 3) {
			return n1;
		} else {
			Vec3D n2 = Vec3D.getNormalUnscaled(getNode(0), getNode(2),
					getNode(3));
			Vec3D sum = Vec3D.add(n1, n2);
			sum.mult(0.5f);
			return sum;
		}
	}

	public float getArea() {
		if (this.getNNodes() == 3) {
			return Vec3D.getTriangleArea(getNode(0), getNode(1), getNode(2));
		}
		return Vec3D.getTriangleArea(getNode(0), getNode(1), getNode(2))
				+ Vec3D.getTriangleArea(getNode(2), getNode(3), getNode(0));
	}

	public Vec3D[] getNodes() {
		return nodes;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	public int getComment() {
		return comment;
	}

	public void setComment(int comment) {
		this.comment = comment;
	}

	public int getNodesNum() {
		// TODO Auto-generated method stub
		return nodes.length;
	}

	public float getCurvature() {
		ArrayList<TopoMeshFace> faces = this.getNbFaces();
		Vec3D center = Vec3D.getGravityCenter(nodes);
		Vec3D normal = this.getNormal();
		Vec3D normalcenter = Vec3D.add(center, normal);
		float sumD = 0;
		for (int i = 0; i < faces.size(); i++) {
			TopoMeshFace nb = faces.get(i);
			Vec3D cCenter = Vec3D.getGravityCenter(nb.nodes);
			Vec3D cNormal = nb.getNormal();
			Vec3D cNormalcenter = Vec3D.add(cCenter, cNormal);
			float cD = center.dist(cCenter);
			float cN = normalcenter.dist(cNormalcenter);
			sumD += (cD - cN);
		}
		return sumD / faces.size();
	}

	public float getJaggedness() {
		return Vec3D.getPerimeter(nodes) / getArea();
	}

	public float getHorizontality() {
		Vec3D normal = getNormal();
		// Vec2D
		// dX= length x,z
		double cL = Math.sqrt(normal.getX() * normal.getX() + normal.getZ()
				* normal.getZ());
		return (float) (Math.atan2(normal.getZ(), cL));

	}

	public float getPlanarity() {
		if (getNNodes() == 3)
			return 0;
		else {
			// Quads
			return Vec3D.getDistanceSignedToPlane(getNode(0), getNode(1),
					getNode(2), getNode(3));
		}
		// Vec3D.getNormal(p1, p2, p3);
	}

	// http://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up
	public float signedVolumeOfTriangle() {
		return Vec3D.signedVolumeOfTriangle(nodes[0], nodes[1], nodes[2]);
	}

	public boolean isLine(float dAngle) {
		PVector v1 = PVector.sub(getNode(1), getNode(0));
		PVector v2 = PVector.sub(getNode(2), getNode(0));
		float angle = TopoMeshNode.getAngle(v1, v2);
		if (angle < dAngle || angle > Math.PI - dAngle) {
			return true;// parallel planes
		}
		return false;
	}

	public boolean isSameVertex(float d) {
		for (int i = 0; i < getNodesNum(); i++) {
			PVector v1 = PVector.sub(getNode(i), getNode((i + 1)));
			if (v1.mag() < d)
				return true;
		}
		return false;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public void displayNormal3D(PGraphics g, float l) {
		PVector n = this.getNormal();
		n.mult(l);
		PVector c = this.getCenter();
		g.line(c.x, c.y, c.z, c.x + n.x, c.y + n.y, c.z + n.z);
	}

	@Override
	public int getNumVertices() {
		// TODO Auto-generated method stub
		// System.out.println("numV: "+nodes.length);
		return nodes.length;
	}

	@Override
	public void getPosition(int i, float[] coords) {
		// TODO Auto-generated method stub
		PVector v = nodes[i];
		coords[0] = v.x;
		coords[1] = v.y;
		coords[2] = v.z;
	}

	@Override
	public void setPosition(int i, float x, float y, float z) {
		// TODO Auto-generated method stub
		TopoMeshNode n = nodes[i];
		n.setPosition(x, y, z);

	}

}
